package at.fpmedv.eclipse.helma.core;

import helma.framework.core.Application;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.preference.IPreferenceStore;

import at.fpmedv.eclipse.helma.preferences.HelmaConstants;

/**
 * representation of the helma core server
 * 
 * @author fpm
 *
 */
public class HelmaServer {

	/**
	 * storage for server.properties in helmaHomePath
	 */
	public static Properties helmaServerProperties;
	
	/**
	 * path to helma home directory
	 */
	String helmaHomePath;
	
	/**
	 * the real helma server
	 */
	private helma.main.Server server = null;
	
	/**
	 * the port for http
	 */
	private String helmaWebPort = "8080";
	
	/**
	 * The name of the extension point for listeners to register
	 */
	private static final String listenerExtentionId = "at.fpmedv.eclipse.helma.core.listener";
	
	/**
	 * list of listeners to notify for server events
	 * @see HelmaServer#getListeners()
	 */
	private List<IHelmaEventListener> listeners;

	/**
	 * The "Eclipse" representation of the application tree
	 */
	private ArrayList<ITreeParent> applications = new ArrayList<ITreeParent>();
	
	/**
	 * starts the server if it's not running and checks Preferences
	 */
	public void startServer() throws HelmaPluginRuntimeException {
		if (server == null) {
			IPreferenceStore store = HelmaCorePlugin.getDefault().getPreferenceStore();
			// check for path set if not throw Exception
			helmaHomePath = store.getString(HelmaConstants.P_HELMA_HOME_PATH);
			if (helmaHomePath == null || helmaHomePath == "") {
				throw new HelmaPluginRuntimeException("Helma home path not set in Preferences");
			}

			if (store.getBoolean(HelmaConstants.P_READ_SERVER_PROPS)) {
				helmaServerProperties = new Properties();
				String helmaServerPropertiesPath = helmaHomePath + "/server.properties";
				try {
					helmaServerProperties.load(new FileInputStream(helmaServerPropertiesPath));
				} catch (FileNotFoundException e) {
					throw new HelmaPluginRuntimeException(helmaServerPropertiesPath + " not found " +  e);
				} catch (IOException e) {
					throw new HelmaPluginRuntimeException("Opening " + helmaServerPropertiesPath + " caused " +  e);
				}
				// look for webPort
				String webPort = helmaServerProperties.getProperty("webPort");
				if (webPort != null)
					helmaWebPort = webPort;
				if (store.getBoolean(HelmaConstants.P_OVERRIDE_LOGDIR_SETTING)) {
					// set logDir = console
					helmaServerProperties.setProperty("logDir", "console");
				} else {
					// set logDir = 			
					helmaServerProperties.setProperty("logDir", "");
				}
				try {
					// new FileWriter(new File(helmaServerPropertiesPath)), "Saved by Helma Eclipse Plugin!"
					helmaServerProperties.store(new FileOutputStream(helmaServerPropertiesPath), "Saved by Helma Eclipse Plugin!");
				} catch (IOException e) {
					HelmaCorePlugin.getDefault().log("Error storing properties logdir may not be set correct", e);
				} catch (java.lang.NoSuchMethodError e) {
					// hmm seems like 1.5
					try {
						helmaServerProperties.save(new FileOutputStream(helmaServerPropertiesPath) , "Saved by Helma Eclipse Plugin!");
					} catch (FileNotFoundException e1) {
						HelmaCorePlugin.getDefault().log("Error storing properties logdir may not be set correct", e1);
					}
				}
				// TODO read/set more props
			}
			try {
				helma.main.ServerConfig serverConfig = null;
				serverConfig = helma.main.Server.getConfig(new String[]{"-w", helmaWebPort, "-h", helmaHomePath});

		        server = new helma.main.Server(serverConfig);

	        	helma.util.EclipseHelmaLoggerDistributor.setLogger(true);
	        	
		        // parse properties files etc
		        server.init();

		        // start the server main thread
		        server.start();
		        fireServerStarted();
			} catch (Throwable e) {
				HelmaCorePlugin.getDefault().log("Error Starting Server", (Exception) e);
				fireErrorStarting();
			}
		} else {
			fireErrorStarting();
			throw new HelmaPluginRuntimeException("Server allready running");
		}
	}
	
    /**
	 * stops the server if it's running
	 */
	public void stopServer() throws HelmaPluginRuntimeException {
		if (server != null) {
			server.stop();
			// reset the applications tree
			applications = new ArrayList<ITreeParent>();
			// remove server
			server = null;
			fireServerStopped();
		} else {
			fireErrorStopping();
			throw new HelmaPluginRuntimeException("Server not running");
		}
	}

	
	/**
	 * just a getter for the listeners. Calls computeLoggers if loggers == null
	 * 
	 * @return list of loggers
	 */
	private List<IHelmaEventListener> getListeners() {
		if (listeners == null) {
			listeners = computeListeners();
		}
		return listeners;
	}

	/**
	 * Find all extensions that extend the extension point and store them for
	 * later usage.
	 * 
	 * @return list of found extensions
	 */
	private List<IHelmaEventListener> computeListeners() {
		IExtensionRegistry registry = Platform.getExtensionRegistry();
		IExtensionPoint extensionPoint = registry.getExtensionPoint(listenerExtentionId);
		IExtension[] extensions = extensionPoint.getExtensions();
		ArrayList<IHelmaEventListener> result = new ArrayList<IHelmaEventListener>();
		for (int i = 0; i < extensions.length; i++) {
			IConfigurationElement[] elements = extensions[i].getConfigurationElements();
			for (int j = 0; j < elements.length; j++) {
				try {
					IHelmaEventListener listener = (IHelmaEventListener) elements[j].createExecutableExtension("class");
					result.add(listener);
				} catch (CoreException e) {
					// do nothing
					HelmaCorePlugin.getDefault().log("listener not an IHelmaEventListener", e);
				}
			}
		}
		return result;
	}
	
	/**
	 * informs all listeners that the server was successfully started
	 */
	private void fireServerStarted() {
		for (Iterator<IHelmaEventListener> all = getListeners().iterator(); all.hasNext(); ) {
			final IHelmaEventListener each = all.next();
			each.serverStarted();
		}
	}

	/**
	 * informs all listeners that the server was successfully stopped
	 */
	private void fireServerStopped() {
		for (Iterator<IHelmaEventListener> all = getListeners().iterator(); all.hasNext(); ) {
			final IHelmaEventListener each = all.next();
			each.serverStoped();
		}
	}

	/**
	 * informs all listeners that we tried to start the server but an error occurred
	 */
	private void fireErrorStarting() {
		for (Iterator<IHelmaEventListener> all = getListeners().iterator(); all.hasNext(); ) {
			final IHelmaEventListener each = all.next();
			each.errorStarting();
		}
	}
	
	/**
	 * informs all listeners that we tried to stop the server but an error occurred
	 */
	private void fireErrorStopping() {
		for (Iterator<IHelmaEventListener> all = getListeners().iterator(); all.hasNext(); ) {
			final IHelmaEventListener each = all.next();
			each.errorStopping();
		}
	}

	public ArrayList<ITreeParent> getApplications() {
		if (server == null || !isRunning()) {
			// do nothing
		} else {
			Object[] helmaApplications = server.getApplications();
			boolean equalApps = true;
			// check if there are different apps
			for (ITreeParent app: applications) {
				boolean found = false;
				// try to find it in current array
				for (Object someAppObj: helmaApplications) {
					Application app2 = (Application) someAppObj;
					if (app2.getName().equals(app.getName())) {
						found = true;
						break;
					}
				}
				if (!found) {
					equalApps = false;
					break;
				}
			}
			if (helmaApplications.length != applications.size() || !equalApps) {
				// remove old ones
				applications = new ArrayList<ITreeParent>();
				for (int i = 0; i < helmaApplications.length; i++) {
					Application app = (Application) helmaApplications[i];
					HelmaAppTreeObject p1 = new HelmaAppTreeObject(app);
					applications.add(p1);
				}
			}
		}
		return applications;
	}

	private boolean isRunning() {
		return server != null;
	}
}
